﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Security.Cryptography;
using System.Text;

namespace Taobao.Open.Platform.Tools
{
    /// <summary>
    /// SPI服务提供方工具类。
    /// </summary>
    public class SpiUtils
    {
        private const string CTYPE_APP_JSON = "application/json";
        private const string CTYPE_TEXT_XML = "text/xml";
        private const string CTYPE_TEXT_PLAIN = "text/plain";
        private const string CTYPE_FORM_DATA = "application/x-www-form-urlencoded";
        private const string TOP_SIGN_LIST = "top-sign-list";
        private const string SIGN = "sign";

        /// <summary>
        /// 校验SPI请求签名，不支持带上传文件的HTTP请求。
        /// </summary>
        /// <param name="header">header对象实例</param>
        /// <param name="query">url请求参数对象实例</param>
        /// <param name="request">HttpRequest对象实例</param>
        /// <param name="requestString">HttpRequest对象字符串</param>
        /// <param name="secret">APP密钥</param>
        /// <returns>签名字符串</returns>
        public static string GetSign(NameValueCollection header, NameValueCollection query, object request, string requestString, string secret)
        {
            try
            {
                if (header == null || query == null || string.IsNullOrEmpty(secret))
                    throw new Exception();
                string ctype = header["Content-Type"];
                if (string.IsNullOrEmpty(ctype) || (!ctype.StartsWith(CTYPE_APP_JSON) && !ctype.StartsWith(CTYPE_TEXT_XML) && !ctype.StartsWith(CTYPE_TEXT_PLAIN) && !ctype.StartsWith(CTYPE_FORM_DATA)))
                    throw new Exception();

                string charset = GetRequestCharset(ctype);
                IDictionary<string, string> parameters = new SortedDictionary<string, string>(StringComparer.Ordinal);

                // 1. 获取header参数
                AddAll(parameters, GetHeaderMap(header));

                // 2. 获取url参数
                Dictionary<string, string> queryMap = GetQueryMap(query);
                AddAll(parameters, queryMap);

                // 4. 生成签名并校验
                string sign = Sign(parameters, requestString, secret, charset);

                return sign;
            }
            catch
            {
                return string.Empty;
            }
        }

        /// <summary>
        /// 校验SPI请求签名，不支持带上传文件的HTTP请求。
        /// </summary>
        /// <param name="header">header对象实例</param>
        /// <param name="query">url请求参数对象实例</param>
        /// <param name="request">HttpRequest对象实例</param>
        /// <param name="requestString">HttpRequest对象字符串</param>
        /// <param name="secret">APP密钥</param>
        /// <returns>校验结果</returns>
        public static bool CheckSign(NameValueCollection header, NameValueCollection query, object request, string requestString, string secret)
        {
            try
            {
                if (header == null || query == null || string.IsNullOrEmpty(secret))
                    throw new Exception();
                string ctype = header["Content-Type"];
                if (string.IsNullOrEmpty(ctype) || (!ctype.StartsWith(CTYPE_APP_JSON) && !ctype.StartsWith(CTYPE_TEXT_XML) && !ctype.StartsWith(CTYPE_TEXT_PLAIN) && !ctype.StartsWith(CTYPE_FORM_DATA)))
                    throw new Exception();

                string sign = GetSign(header, query, request, requestString, secret);
                if (!sign.Equals(query[SIGN]))
                    throw new Exception();

                return true;
            }
            catch
            {
                return false;
            }
        }

        private static void AddAll(IDictionary<string, string> dest, IDictionary<string, string> from)
        {
            if (from != null && from.Count > 0)
            {
                IEnumerator<KeyValuePair<string, string>> em = from.GetEnumerator();
                while (em.MoveNext())
                {
                    KeyValuePair<string, string> kvp = em.Current;
                    dest.Add(kvp.Key, kvp.Value);
                }
            }
        }

        /// <summary>
        /// 签名规则：hex(md5(secret+sorted(header_params+url_params+form_params)+body)+secret)
        /// </summary>
        private static string Sign(IDictionary<string, string> parameters, string body, string secret, string charset)
        {
            IEnumerator<KeyValuePair<string, string>> em = parameters.GetEnumerator();

            // 第1步：把所有参数名和参数值串在一起
            StringBuilder query = new StringBuilder(secret);
            while (em.MoveNext())
            {
                string key = em.Current.Key;
                if (!SIGN.Equals(key))
                {
                    string value = em.Current.Value;
                    query.Append(key).Append(value);
                }
            }
            if (body != null)
            {
                query.Append(body);
            }

            query.Append(secret);

            // 第2步：使用MD5加密
            MD5 md5 = MD5.Create();
            byte[] bytes = md5.ComputeHash(Encoding.GetEncoding(charset).GetBytes(query.ToString()));

            // 第3步：把二进制转化为大写的十六进制
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < bytes.Length; i++)
            {
                result.Append(bytes[i].ToString("X2"));
            }

            return result.ToString();
        }

        public static Dictionary<string, string> GetHeaderMap(NameValueCollection header)
        {
            Dictionary<string, string> headerMap = new Dictionary<string, string>();
            if (header != null)
            {
                string signList = header[TOP_SIGN_LIST];
                if (!string.IsNullOrEmpty(signList))
                {
                    string[] keys = signList.Split(',');
                    foreach (string key in keys)
                        headerMap[key] = header[key];
                }
            }
            return headerMap;
        }

        public static Dictionary<string, string> GetQueryMap(NameValueCollection query)
        {
            Dictionary<string, string> queryMap = new Dictionary<string, string>();
            if (query != null)
            {
                foreach (var key in query.AllKeys)
                    queryMap[key] = query[key];
            }
            return queryMap;
        }

        public static string GetRequestCharset(string ctype = null)
        {
            string charset = "utf-8";
            if (!string.IsNullOrEmpty(ctype))
            {
                string[] entires = ctype.Split(';');
                foreach (string entry in entires)
                {
                    string _entry = entry.Trim();
                    if (_entry.StartsWith("charset"))
                    {
                        string[] pair = _entry.Split('=');
                        if (pair.Length == 2)
                        {
                            if (!string.IsNullOrEmpty(pair[1]))
                            {
                                charset = pair[1].Trim();
                            }
                        }
                        break;
                    }
                }
            }
            return charset;
        }
    }
}
